﻿using System;
using SoftwareConsulting.BI11.NumericalMethods.Interfaces;
using SoftwareConsulting.BI11.NumericalMethods.DhbFunctionEvaluation;
using SoftwareConsulting.BI11.NumericalMethods.Iterations;
using SoftwareConsulting.BI11.Studio.InternalServices;

namespace SoftwareConsulting.BI11.NumericalMethods.Statistics
{    
    /// <summary>
    /// Класс распределения Стюдента для выполнения T-теста
    /// </summary>
    public sealed class StudentDistribution : ProbabilityDensityFunction
    {	    
        private int _degreeOfFreedom;//степень свободы	    
        private double _norm;//норма (для ускорения)	    
        private IncompleteBetaFunction _incompleteBetaFunction;//функция для вычисления распределения	    
        private ChiSquareDistribution _chiSquareDistribution;//вспомогательное распределения для генерации случайнх чисел

        /// <summary>
        /// Конструктор класса
        /// </summary>
        /// <param name="degreeOfFreedom"></param>
        public StudentDistribution(int degreeOfFreedom)
        {
            if (degreeOfFreedom <= 0)
                throw new ArgumentOutOfRangeException("degreeOfFreedom");
            SetParameters(degreeOfFreedom);
        }

        /// <summary>
        /// Конструктор класса
        /// </summary>
        /// <param name="histogram">Гистограмма, из которой вытягиваются параметры по наилучшим предположениям</param>
        public StudentDistribution(IHistogram histogram)
        {
            double variance = histogram.Variance;
            if (variance <= 0)
                throw new ArgumentOutOfRangeException();
            SetParameters((int)Math.Max(1, Math.Round(2 / (1 - 1 / variance))));
        }

        /// <summary>
        /// Возвращает среднее распределения
        /// </summary>
        public override double Average
        {
            get 
            { 
                return 0; 
            }
            set 
            {
                Assert.Fail(Assert.Id.Assert00291);//для этого распределения не поддерживается
            }
        }

        /// <summary>
        /// Возвращает уровень доверия
        /// </summary>
        /// <param name="x"></param>
        /// <returns></returns>
        public double GetConfidenceLevel(double x)
        {            
            return x < 0 ? double.NaN : (1 - GetSymmetricAcceptance(x)) * 100;
        }

        /// <summary>
        /// Задает новые параметры
        /// </summary>        
        /// <param name="degreeOfFreedom">степень свободы</param>
        public void SetParameters(int degreeOfFreedom)
        {
            _degreeOfFreedom = degreeOfFreedom;
            _norm = -(Math.Log(_degreeOfFreedom) * 0.5 + GammaFunction.LogBeta(_degreeOfFreedom * 0.5, 0.5));
        }

        /// <summary>
        /// Возвращает значение функции плотности распределения
        /// </summary>
        /// <param name="x"></param>
        /// <returns>вероятность, нахождения случайной переменной меньше или равной, чем X</returns>
        public override double GetDistributionValue(double x)
        {
            if (x == 0)
                return 0.5;
            double acc = GetSymmetricAcceptance(Math.Abs(x));
            return x > 0 ? 1 + acc : 1 - acc;
        }

        /// <summary>
        /// Возвращает незавершенную бета-функцию (функцию для вычисления распределения)
        /// </summary>
        private IncompleteBetaFunction IncompleteBetaFunction
        {
            get
            {
                if (_incompleteBetaFunction == null)
                    _incompleteBetaFunction = new IncompleteBetaFunction(_degreeOfFreedom / 2, 0.5);
                return _incompleteBetaFunction;
            }
        }

        /// <summary>
        /// Возвращает эксцесс распределения
        /// </summary>
        public override double Kurtosis
        {
            get
            { 
                return _degreeOfFreedom > 4 ? 
                    6 / (_degreeOfFreedom - 4) 
                    : 
                    double.NaN; 
            }
        }

        /// <summary>
        /// Возвращает название распределения
        /// </summary>
        public override string Name
        {
            get
            { 
                return "Student distribution"; 
            }
        }

        /// <summary>
        /// Возвращает/устанавливает параметры распределения
        /// </summary>
        public override double[] Parameters
        {
            get 
            { 
                return new double[] { _degreeOfFreedom }; 
            }
            set 
            { 
                SetParameters((int)Math.Round(value[0])); 
            }
        }

        /// <summary>
        /// Возвращает случайное вещественное число в соответствие с распределением
        /// </summary>
        /// <returns></returns>
        public override double GetRandomValue()
        {
            if (_chiSquareDistribution == null)
                _chiSquareDistribution = new ChiSquareDistribution(_degreeOfFreedom - 1);            
            return RandomGenerator.NextGaussian() * Math.Sqrt((_degreeOfFreedom - 1) / _chiSquareDistribution.GetRandomValue());
        }

        /// <summary>
        /// Возвращает коэффициент уклона распределения
        /// </summary>
        public override double Skewness
        {
            get 
            { 
                return 0; 
            }
        }

        /// <summary>
        /// Возвращает симметричное принятие
        /// </summary>
        /// <param name="x"></param>
        /// <returns>от -x до x</returns>
        private double GetSymmetricAcceptance(double x)
        {
            return IncompleteBetaFunction.GetValue(_degreeOfFreedom / (x * x + _degreeOfFreedom));
        }

        /// <summary>
        /// Вычисляет значение, основываясь на функции плотности
        /// </summary>
        /// <param name="x"></param>
        /// <returns></returns>
        public override double GetValue(double x)
        {
            return Math.Exp(_norm - Math.Log(x * x / _degreeOfFreedom + 1) * (_degreeOfFreedom + 1) / 2);
        }

        /// <summary>
        /// Возвращает дисперсию распределения
        /// </summary>
        public override double Variance
        {
            get 
            { 
                return _degreeOfFreedom > 2 ? _degreeOfFreedom / (_degreeOfFreedom - 2) : double.NaN; 
            }
        }
    }
}
